package com.hyt.it.ogt.kq.service.gov.arrange;

import com.alibaba.fastjson.JSON;
import com.hyt.core.base.BaseEntity;
import com.hyt.core.util.UUIDUtils;
import com.hyt.it.ogt.kq.common.config.exception.KqException;
import com.hyt.it.ogt.kq.common.enums.ArrangeRuleEnum;
import com.hyt.it.ogt.kq.common.gov.constant.ArrangeParamCodeEnum;
import com.hyt.it.ogt.kq.common.gov.constant.ResponseCode;
import com.hyt.it.ogt.kq.common.gov.constant.StringCodeEnum;
import com.hyt.it.ogt.kq.common.gov.utils.StringUtil;
import com.hyt.it.ogt.kq.service.gov.model.entity.*;
import com.hyt.it.ogt.kq.service.gov.model.param.ArrangeParam;
import com.hyt.it.ogt.kq.service.gov.model.vo.TaskPlaceVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.examiner.TimeRoomParamVO;
import com.hyt.it.ogt.kq.service.gov.schedule.ExamRoomSchedule;
import com.hyt.it.ogt.kq.service.gov.schedule.impl.ExamRoomScheduleImpl;
import com.hyt.it.ogt.kq.service.gov.schedule.model.*;
import com.hyt.it.ogt.kq.service.model.entity.Candidate;
import com.hyt.it.ogt.kq.service.model.entity.TaskCandidateArea;
import com.hyt.loginfo.model.Organization;
import com.hyt.progress.service.ProgressManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 系统自动编排，包括随机编排
 */
@Slf4j
@Component
public class VirtualRoomArrangeAdapter extends ArrangeAbstractService {

    @Override
    public Object arrange(ArrangeParam param) {
        this.check(param);
        ProgressManager.updateProgress(9L);
        // 重新编排的情况下，删除所有编排数据
        timeRoomCandidateMapper.deleteArrangeCandidateByTaskId(param.getTaskId());
        ProgressManager.updateProgress(15L);
        ScheduleConstrain scheduleConstrain = this.buildScheduleConstrain(param);
        ProgressManager.updateProgress(18L);

        List<Organization> officeAllDepts = iUaDeptService.getOfficeAllDepts();
        Map<String, Organization> officeMap = officeAllDepts.stream()
                .collect(Collectors.toMap(Organization::getId, Function.identity()));

        List<ExamInfo> examInfos = this.getExamInfoList(param, scheduleConstrain.getGroupAttrs().size(), officeMap);

        ProgressManager.updateProgress(35L);

        scheduleConstrain.setBatchNumber(examInfos.size());

        ExamRoomSchedule examRoomSchedule = new ExamRoomScheduleImpl();

        ImmutablePair<List<ScheduleResult>, List<ExamRoom>> result = examRoomSchedule.scheduleVirRoom(scheduleConstrain,
                examInfos);
        ProgressManager.updateProgress(65L);
        if (result != null && result.getLeft().size() > 0) {
            Optional<ScheduleResult> first = result.getLeft().stream()
                    .filter(item -> item.getExamRooms() != null && item.getExamRooms().size() > 0).findFirst();
            if (first.isPresent()) {
                this.saveTimeRoomCandidate(param, result.getLeft());
            } else {
                throw new KqException(ResponseCode.ARRANGE_FAIL.getCode(), ResponseCode.ARRANGE_FAIL.getMsg());
            }
        } else {
            throw new KqException(ResponseCode.ARRANGE_FAIL.getCode(), ResponseCode.ARRANGE_FAIL.getMsg());
        }
        return null;
    }

    /**
     * 虚拟考场或者实体考场都在此时构造TaskRoom数据入库
     *
     * @param param
     * @param left
     */
    protected void saveTimeRoomCandidate(ArrangeParam param, List<ScheduleResult> left) {
        // 任务参数
        Map<String, String> paramMap = iTimeRoomParamService.getTimeRoomParamMap(param.getTimeRoomParamVOList(),
                param.getTaskId());
        ProgressManager.updateProgress(70L);
        // 编排数据
        List<TimeRoomCandidate> timeRoomCandidates = new ArrayList<>();
        Map<String, TaskPlace> keyPlaceMap = new HashMap<>();

        List<TaskRoom> taskRoomList = new ArrayList<>();
        List<TaskPlace> taskPlaceList = new ArrayList<>();
        List<TimeRoom> timeRoomSaveList = new ArrayList<>();

        Map<String, String> admissionMap = new HashMap<>();
        // 编排规则
        Optional<TimeRoomParamVO> opt = param.getTimeRoomParamVOList().stream()
                .filter(item -> StringCodeEnum.ARRANGE_PARAM_SAME_ADMISSION_NUMBER.getCode()
                        .equalsIgnoreCase(item.getCode()))
                .findFirst();
        Optional<TimeRoomParamVO> rule = param.getTimeRoomParamVOList().stream()
                .filter(item -> StringCodeEnum.ARRANGE_PARAM_ARRANGE_RULE.getCode().equalsIgnoreCase(item.getCode()))
                .findFirst();
        int arrangeRule = Integer.valueOf(rule.get().getValue());
        // 是否同一准考证
        boolean sameAdmissionFlag = Boolean.valueOf(opt.get().getValue());

        // 准考证生成方式
        Integer admissionType = new Integer(paramMap.get(StringCodeEnum.ARRANGE_PARAM_ADMISSION_TYPE.getCode()));
        // 自定义准考证，自定义准考证默认使用同一准考证
        Map<String, String> customArrangeMap = super.getCustomArrange(admissionType, param.getTaskId());

        int placeCode = 0;
        int timeRoomCode = 0;

        Map<String, TaskPlace> placeInfoMap = new HashMap<>();
        Map<String, Integer> placeRoomNumberMap = new HashMap<>();
        Map<String, TaskRoom> roomMap = new HashMap<>();

        log.debug("=========虚拟考场编排数据返回结果===={}======", JSON.toJSONString(left));

        for (ScheduleResult item : left) { // 遍历批次
            for (ScheduleExamRoom e : item.getExamRooms()) { // 遍历考场
                String roomId = e.getRoomId().replaceAll("-", "");
                String placeKey = null;
                String province = null;
                String city = null;
                String district = null;
                if (ArrangeRuleEnum.DEPT.getCode() == arrangeRule) {
                    placeKey = e.getDept(); // 以机构为维度生成考点
                } else if (ArrangeRuleEnum.SELF.getCode() == arrangeRule) { // 按照区域层级生成考点
                    province = e.getProvince();
                    if(StringUtils.isBlank(e.getCity())){ // 表示一级编排
                        // 随机拿一个考生同省下的市，补足数据，考生的区域必须有三级
                        Candidate one = iCandidateService.lambdaQuery().eq(Candidate::getTaskId, param.getTaskId())
                                .eq(Candidate::getProvince, province).last("limit 1").one();
                        city = one.getCity();
                        district = one.getDistrict();
                    } else {
                        city = e.getCity();
                    }
                    if(StringUtils.isBlank(e.getDistrict()) && StringUtils.isBlank(district)) {  // 表示表示二级编排
                        // 随机拿一个考生同市下的区县，补足数据，考生的区域必须有三级
                        Candidate one = iCandidateService.lambdaQuery().eq(Candidate::getTaskId, param.getTaskId())
                                .eq(Candidate::getProvince, province).eq(Candidate::getCity, city).last("limit 1").one();
                        district = one.getDistrict();
                    } else { // 3级编排
                        district = e.getDistrict();
                    }
                    placeKey = province + city + district;
                    // 区域数据入库
                    this.insertTaskArea(param.getTaskId(), Arrays.asList(province, city, district));
                } else {
                    // 随机编排，只生成一个考点
                    placeKey = DEFAULT;
                }

                // 判断考点是否存在
                TaskPlace taskPlace = keyPlaceMap.get(placeKey);

                if(taskPlace == null) {
                    // 初始化虚拟考点
                    placeCode++;
                    taskPlace = new TaskPlace();
                    taskPlace.setTaskId(param.getTaskId())
                            .setCode(placeCode)
                            .setName("虚拟考点" + placeCode)
                            .setDeptId(e.getDept())
                            .setProvince(province)
                            .setCity(city)
                            .setDistrict(district)
                            .setId(UUIDUtils.randomUUID());
                    taskPlaceList.add(taskPlace);
                    // 以一定维度记录考点
                    keyPlaceMap.put(placeKey, taskPlace);
                    // 根据考点ID拿考点信息
                    placeInfoMap.put(taskPlace.getId(), taskPlace);
                    // 记录考点考场数量
                    placeRoomNumberMap.put(taskPlace.getId(), 0);
                }

                //判断考场是否存在
                TaskRoom taskRoom1 = roomMap.get(roomId);
                if(taskRoom1 == null) {
                    // 考点下的考场数量+1
                    Integer roomNum = placeRoomNumberMap.get(taskPlace.getId()) + 1;
                    TaskRoom taskRoom = new TaskRoom();
                    taskRoom.setTaskId(param.getTaskId())
                            .setPlaceId(taskPlace.getId())
                            .setSeatNum(e.getCandidates().size())
                            .setAddress("无")
                            .setDeptId(taskPlace.getDeptId())
                            .setReserveSeatNum(0)
                            .setAnswerType(1) // 虚拟考场默认都是电子作答方式
                            .setReserve(false)
                            .setRegistered(false)
                            .setClosure(false)
                            .setId(roomId);
                    // 记录考场ID和考场信息关系
                    roomMap.put(roomId, taskRoom);
                    // 考点下考场数量+1后重新设值
                    placeRoomNumberMap.put(taskPlace.getId(), roomNum);
                    // 加入需要入库的考场
                    taskRoomList.add(taskRoom);
                } else {
                    // 以最大的考生数量作为考场的机位数
                    if(taskRoom1.getSeatNum() < e.getCandidates().size()) {
                        taskRoom1.setSeatNum(e.getCandidates().size());
                    }
                }
            }
        }

        // 按照每个考点下的考场机位数重新排序，降序排序
        LinkedHashMap<String, List<TaskRoom>> collect = taskRoomList.stream().sorted(Comparator.comparingInt(TaskRoom::getSeatNum))
                .collect(Collectors.groupingBy(TaskRoom::getPlaceId, LinkedHashMap::new, Collectors.toList()));
        // 重新排序考场号，把编排人数多的考场排在前面
        collect.forEach((k,v) -> {
            int roomCode = v.size();
            for (TaskRoom taskRoom : v) {
                taskRoom.setCode(roomCode);
                taskRoom.setName("虚拟考场" + roomCode);
                --roomCode;
            }
        });

        // 编排结果入库，允许部分编排成功，部分失败
        for (ScheduleResult item : left) { // 遍历批次
            for (ScheduleExamRoom e : item.getExamRooms()) { // 遍历考场
                String timeRoomId = UUIDUtils.randomUUID();
                String roomId = e.getRoomId().replaceAll("-", "");
                // 拿到考场
                TaskRoom taskRoom = roomMap.get(roomId);
                // 根据考场拿到考点
                TaskPlace taskPlace = placeInfoMap.get(taskRoom.getPlaceId());

                // 自定义准考证按省市县生成
                TaskPlaceVO taskPlaceVO = TaskPlaceVO.builder().id(taskPlace.getId()).province(taskPlace.getProvince())
                        .city(taskPlace.getCity()).district(taskPlace.getDistrict()).build();

                // 构造场次表数据
                TimeRoom timeRoom = new TimeRoom();
                timeRoom.setTimeId(item.getBatchNo())
                        .setTaskId(param.getTaskId())
                        .setRoomId(roomId)
                        .setCode(String.valueOf(timeRoomCode++))
                        .setMaxCount(e.getCapacity())
                        .setPlanCount(e.getCapacity())
                        .setActualCount(e.getCandidates().size())
                        .setId(timeRoomId);
                timeRoomSaveList.add(timeRoom);
                // 构造场次考生数据
                for (ScheduleExamRoom.ScheduleCandidate c : e.getCandidates()) {
                    TimeRoomCandidate timeRoomCandidate = TimeRoomCandidate.builder().taskId(item.getScheduleTaskId())
                            .timeId(item.getBatchNo())
                            .timeRoomId(timeRoomId).roomId(roomId).candidateId(c.getBmStudentId())
                            .subjectId(c.getSubject())
                            .candidateSubjectId(c.getCandidateSubjectId())
                            .seatNum(StringUtil.stringFormatZero(c.getPosition(), 3))
                            .placeId(taskPlace.getId())
                            .build();
                    String zkz = null;
                    // 同一个考生多科目同一准考证
                    if (sameAdmissionFlag) {
                        zkz = admissionMap.get(c.getBmStudentId());
                    }

                    if (zkz == null) {
                        // 获取准考证号生成方式
                        if (ArrangeParamCodeEnum.ADMISSION_TYPE_CUSTOM.getCode().equals(admissionType)) {
                            // 使用自定义准考证号
                            zkz = customArrangeMap.get(c.getBmStudentId());
                            admissionMap.put(c.getBmStudentId(), zkz);
                        } else {
                            if (ArrangeRuleEnum.SELF.getCode() != arrangeRule) {
                                // 写死，除了按照区域层级划分，另外两种默认一个，保持原来生成准考证号逻辑
                                taskPlaceVO.setProvince("10");
                                taskPlaceVO.setCity("01");
                                taskPlaceVO.setDistrict("01");
                            }
                            zkz = admissionService.getAdmissionNumber(param.getTaskId(), paramMap, taskPlaceVO);
                            if (sameAdmissionFlag) {
                                // 保持考生多科目准考证号一致
                                admissionMap.put(c.getBmStudentId(), zkz);
                            }
                        }
                    }
                    timeRoomCandidate.setAdmissionNum(zkz);
                    timeRoomCandidates.add(timeRoomCandidate);
                }
            }
        }
        ProgressManager.updateProgress(85L);

        if (CollectionUtils.isNotEmpty(timeRoomCandidates)) { // 虚拟编排需要重置部分表数据为假删
            // 删除所有考点数据
            iTaskPlaceService.lambdaUpdate().set(BaseEntity::getDelFlag, true)
                    .eq(TaskPlace::getTaskId, param.getTaskId()).eq(BaseEntity::getDelFlag, false).update();
            // 删除所有考场数据
            iTaskRoomService.lambdaUpdate().set(BaseEntity::getDelFlag, true).eq(TaskRoom::getTaskId, param.getTaskId())
                    .eq(BaseEntity::getDelFlag, false).update();
            // 删除忽略考场数据
            iTimeRoomExcludeService.lambdaUpdate().set(BaseEntity::getDelFlag, true)
                    .eq(TimeRoomExclude::getTaskId, param.getTaskId()).eq(BaseEntity::getDelFlag, false).update();
            // 删除场次表数
            iTimeRoomService.lambdaUpdate().set(BaseEntity::getDelFlag, true).eq(TimeRoom::getTaskId, param.getTaskId())
                    .eq(BaseEntity::getDelFlag, false).remove();

            // 插入考场
            iTaskRoomService.saveBatch(taskRoomList, 1000);
            // 插入考点
            iTaskPlaceService.saveBatch(taskPlaceList, 1000);
            // 插入场次
            iTimeRoomService.saveBatch(timeRoomSaveList, 1000);
            // 插入编排数据
            iTimeRoomCandidateService.saveBatch(timeRoomCandidates, 1000);
        }
    }

    /**
     * 虚拟考场模式，考点的区域是考生的区域，需要把考生的区域迁移过来
     * @param taskId
     * @param areaList
     */
    private void insertTaskArea(String taskId, List<String> areaList) {
        for (String areaId : areaList) {
            // 判断区域是否存在表中，存在不再重复插入
            if (iTaskAreaService.lambdaQuery().eq(TaskArea::getTaskId, taskId).eq(TaskArea::getAreaId, areaId)
                    .eq(BaseEntity::getDelFlag, false).count() == 0) {
                // 从考生区域中拿详细的区域信息
                TaskCandidateArea one = iTaskCandidateAreaService.lambdaQuery().eq(TaskCandidateArea::getTaskId, taskId).eq(BaseEntity::getDelFlag, false).last("limit 1").one();
                TaskArea taskArea = new TaskArea();
                taskArea.setId(UUIDUtils.randomUUID());
                taskArea.setTaskId(taskId);
                taskArea.setAreaId(areaId);
                taskArea.setName(one.getName());
                taskArea.setParentId(one.getParentId());
                taskArea.setParentIds(one.getParentIds()).getParentIds();
                taskArea.setType(one.getType());
                iTaskAreaService.save(taskArea);
            }
        }
    }

    private void check(ArrangeParam param) {
        // 如果存在编排数据则，不允许解除
        if (iTimeRoomCandidateService.countArrangedCandidate(param.getTaskId()) > 0) {
            throw new KqException(ResponseCode.ERROR_OVERVIEW_HAS_ARRANGED.getCode(),
                    ResponseCode.ERROR_OVERVIEW_HAS_ARRANGED.getMsg());
        }
        // 判断批次是否绑定科目
        if (iTimeSubjectService.lambdaQuery().eq(TimeSubject::getTaskId, param.getTaskId())
                .eq(BaseEntity::getDelFlag, false).count() == 0) {
            throw new KqException(ResponseCode.ARRANGE_CANDIDATE_NOT_TIME_ROOM.getCode(),
                    ResponseCode.ARRANGE_CANDIDATE_NOT_TIME_ROOM.getMsg());
        }
        // 考试任务绑定报名的情况下，需要校验科目数据
        iCandidateSubjectService.checkCandidateSubjectUpdateSubjectCodeOrIdNumber(param.getTaskId());
        super.commonCheck(param);
    }
}
